package weka.learning.semisupervised;

import java.util.ArrayList;

import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.bayes.NaiveBayes;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
 
public class CoTraining {
 
	private Instances setL;                 //Sub-conjunto de dados rotulados
	private Instances setU; 		//Sub-conjunto de dados n�o-rotulados
	private Classifier classifierH1; 	//Classificador treinado a partir do cojunto L
	private Classifier classifierH2; 	//Classificador treinado a partir do cojunto L
	private Instances subSetL1; 		//Sub-conjunto U' que ir� conter todos os exemplos de U rotulados por H
	private Instances subSetL2; 		//Sub-conjunto U' que ir� conter todos os exemplos de U rotulados por H
	private Instances subSetU1; 		//Sub-conjunto U' que ir� conter todos os exemplos de U rotulados por H
	private Instances subSetU2; 		//Sub-conjunto U' que ir� conter todos os exemplos de U rotulados por H
	private String informaçãodeSaida;
	
	public String Executar(Instances setOriginal, Classifier algoClassificacao1, Classifier algoClassificacao2,
			Instances instances1, Instances instances2) throws Exception{
    	
    	/** Iniciando Algoritmo do Self-Training */
    	
    	// Separa o conjunto de teste em L (rotulados) e U (n�o-rotulados)
    	separaConjunto(setOriginal);
    	// Imprime cada sub-conjunto (L e U)
    	informaçãodeSaida += setL.toString()+"\n";
    	informaçãodeSaida += setU.toString();
    	
    	/** Condi��o enquanto ainda tiver elementos no conjunto de U */
    	while(setU.numInstances() > 0) {
    		divideAtributos( instances1, instances2 );
	    	criaClassificadorH( algoClassificacao1, algoClassificacao2 );		
	    	/*
	    	classificaU(); 								// gerando U' a partir do classificador H
	    	System.out.println(subSetU.toString());
	    	*/
	    	melhoresExemplos();							// seleciona as melhores int�ncias de U' e as adiciona em L
    	} /** Fim do while..*/
    	
    	informaçãodeSaida += "\n \n RESULTADO FINAL	\n";
    	informaçãodeSaida += setL.toString()+"\n";
    	
    	return informaçãodeSaida;
    }
	
	/**  Separa um conjunto de instancias em dois sub-conjuntos:
	 *   Sub-conjunto L: instancias rotuladas
	 *   Sub-conjunto U: instancias n�o rotuladas
	 *  
	 *  @param setOriginal Instances - conjunto de dados original
	 */
	public void separaConjunto(Instances setOriginal) {
        
		//Cria o sub-conjunto L
		setL = new Instances(setOriginal); //cria uma c�pia do conjunto original                 
        setL.setRelationName("Set L");     //nomeia o sub-conjunto L
        
        //Percorre todo o sub-conjunto L para eliminar todos os elementos sem-r�tulos
        for( int i = 0; i < setL.numInstances(); i++ ) {
			if( setL.instance(i).isMissing(setL.numAttributes() - 1)){ //verifica se o exemplo possui valor de classe 
				setL.delete(i);
				i--;
			}	 
        }
        
        //Cria o sub-conjunto U                   
      	setU = new Instances(setOriginal); //cria uma c�pia do conjunto original          
      	setU.setRelationName("Set U"); 	   //nomeia o sub-conjunto U
      	
        //Percorre todo o sub-conjunto U para eliminar todos os elementos com r�tulos         
        for( int i = 0; i < setU.numInstances(); i++ ) {
        	//verifica se o valor do atributo classe � diferente de null
	    	if( !setU.instance(i).isMissing(setU.numAttributes() - 1) ) {
	       		setU.delete(i);
	       		i--;  		 
	   	  	}
        }
        
	}
	
	/** Gera o classificador H com base no conjunto dos dados rotulados
	 *  
	 *   @param  algClassificacao Classifier - o algoritmo de classifica��o definido pelo usu�rio
	 *   
	 *   @return Evaluation - avalia��o feita do classificador
	 */
	public void criaClassificadorH(Classifier algClassificacao1, Classifier algClassificacao2) {
		
		Evaluation eTest;
		classifierH1 = algClassificacao1;
		classifierH2 = algClassificacao2;
		
		eTest = null;
		try {
			classifierH1.buildClassifier(setL); // utilizando o conjunto L para treinamento
	        eTest = new Evaluation(setL);
	        eTest.evaluateModel(classifierH1, setL); // testando o modelo gerado a partir do treinamento
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		imprimeResultadoH(eTest);
		
		eTest = null;
		try {
			classifierH2.buildClassifier(setL); // utilizando o conjunto L para treinamento
	        eTest = new Evaluation(setL);
	        eTest.evaluateModel(classifierH2, setL); // testando o modelo gerado a partir do treinamento
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		imprimeResultadoH(eTest);		
	}
	
	/**
	 * 
	 * @param instances1
	 * @param instances2
	 * @return
	 */
	public void divideAtributos( Instances instances1, Instances instances2 ) {
		
		
	}
	
	/**
	 * 
	 */
	public void classificaU() {
		
	}
	
	/**
	 * 
	 */
	public void melhoresExemplos(){
		
		ArrayList <Integer> listaSelecionados = new ArrayList();		
		
		for(int i = 0; i < subSetL1.numInstances(); i++) {
			//Analisando se a classe de L'1 � igual a L'2		
			if( subSetL1.instance(i).stringValue(subSetL1.classIndex())
					.equals(subSetL2.instance(i).stringValue(subSetL2.classIndex())) ){
				//guardando o index de instancia para poder remover as mesmas intancias  de U				
				listaSelecionados.add(i);				
				setU.instance(i).setValue((Attribute)setU.classAttribute(),subSetL1.instance(i).value(subSetL1.classIndex()) );							
			}
		}
		
		//  L U L"  .. adicionando todos selecionados de L" para L
		for(Integer i: listaSelecionados)
			setL.add(setU.instance(i));
		
		//Apagando instancias de U
		for( int i= listaSelecionados.size() ; i >= 0 ; i-- )
			setU.delete(i);
	}
	
	/**
	 * M�todo para imprimir informa��es do classificador e matriz de confus�o.
	 * 
	 * @param test Evaluation - Avalia��o disponivel depois de gerar o classificador.
	 */
	public void imprimeResultadoH(Evaluation test) {
		String strSummary = test.toSummaryString();
		informaçãodeSaida += " \n" + strSummary;
	
        // Get the confusion matrix
        double[][] cmMatrix = test.confusionMatrix();
        for(int row_i = 0; row_i < cmMatrix.length; row_i++){
            for(int col_i = 0; col_i < cmMatrix.length; col_i++){
            	informaçãodeSaida += cmMatrix[row_i][col_i];
            	informaçãodeSaida += "|";
            }
            informaçãodeSaida += "\n";
        }
	}

	/**
	 * @return the setU
	 */
	public Instances getSetU() {
		return setU;
	}

	/**
	 * @param setU the setU to set
	 */
	public void setSetU(Instances setU) {
		this.setU = setU;
	}

	/**
	 * @return the setL
	 */
	public Instances getSetL() {
		return setL;
	}

	/**
	 * @param setL the setL to set
	 */
	public void setSetL(Instances setL) {
		this.setL = setL;
	}

	/**
	 * @return the classifierH1
	 */
	public Classifier getClassifierH1() {
		return classifierH1;
	}

	/**
	 * @param classifierH1 the classifierH1 to set
	 */
	public void setClassifierH1(Classifier classifierH1) {
		this.classifierH1 = classifierH1;
	}

	/**
	 * @return the classifierH2
	 */
	public Classifier getClassifierH2() {
		return classifierH2;
	}

	/**
	 * @param classifierH2 the classifierH2 to set
	 */
	public void setClassifierH2(Classifier classifierH2) {
		this.classifierH2 = classifierH2;
	}

	/**
	 * @return the subSetL1
	 */
	public Instances getSubSetL1() {
		return subSetL1;
	}

	/**
	 * @param subSetL1 the subSetL1 to set
	 */
	public void setSubSetL1(Instances subSetL1) {
		this.subSetL1 = subSetL1;
	}

	/**
	 * @return the subSetL2
	 */
	public Instances getSubSetL2() {
		return subSetL2;
	}

	/**
	 * @param subSetL2 the subSetL2 to set
	 */
	public void setSubSetL2(Instances subSetL2) {
		this.subSetL2 = subSetL2;
	}

	/**
	 * @return the subSetU1
	 */
	public Instances getSubSetU1() {
		return subSetU1;
	}

	/**
	 * @param subSetU1 the subSetU1 to set
	 */
	public void setSubSetU1(Instances subSetU1) {
		this.subSetU1 = subSetU1;
	}

	/**
	 * @return the subSetU2
	 */
	public Instances getSubSetU2() {
		return subSetU2;
	}

	/**
	 * @param subSetU2 the subSetU2 to set
	 */
	public void setSubSetU2(Instances subSetU2) {
		this.subSetU2 = subSetU2;
	}
}